package xcore

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"gitee.com/go-mid/infra/xlog"
	"github.com/golang/protobuf/proto"
	"regexp"
	"strings"
)

// 通用的手机号解析，以及检查
// 手机号格式为：国家区号-手机号
// 国家区号和手机号使用"-"进行分割
// 例如： 86-15000000000
func SplitPhone2AreaPhone(phone string, isDefault86 bool) (area string, number string) {
	area = ""
	if isDefault86 {
		area = "86"
	}
	idx := strings.Index(phone, "-")
	if idx == -1 {
		number = phone
	} else {
		area = phone[:idx]
		number = phone[idx+1:]
	}

	return
}

// 获取带区号的手机号
func WorldPhone(area, number string) string {
	if len(area) == 0 {
		area = "86"
	}
	return area + "-" + number
}

// 输入的是一个可能不带区号，也可能带的号
// 返回一个规范的，肯定带着区号的表示
func WorldPhoneFmt(ctx context.Context, phone string) string {
	area, number := ParseFullPhone(ctx, phone, true)

	return WorldPhone(area, number)
}

//解析手机号成area和phone两部分
//1、规则所有+会被清掉，所有空格会被清除
//2、如果有-，则第一个-之前的部分为area，剩下的部分将所有-去掉作为phone
//e.g. 86-13111111111 => 86  13111111111
//+86-13111111111 => 86  13111111111
//+86-1311 1111 111 => 86  13111111111
//+86-1311-1111-111 => 86  13111111111
func ParseFullPhone(ctx context.Context, fullphone string, isDefault86 bool) (area, phone string) {
	area = ""
	if isDefault86 {
		area = "86"
	}
	fullphone = strings.TrimSpace(fullphone)
	fullphone = strings.ReplaceAll(fullphone, " ", "")
	fullphone = strings.ReplaceAll(fullphone, "+", "")
	if len(fullphone) == 0 {
		return area, ""
	}
	if strings.Index(fullphone, "-") == -1 {
		return area, fullphone
	}
	split := strings.SplitN(fullphone, "-", 2)
	if len(split) == 2 {
		phone = strings.ReplaceAll(split[1], "-", "")
		return split[0], phone
	}
	return area, ""
}

//根据手机号得到用于拨号的手机号格式。
//参考ParsePhone函数格式得到area和phone
//如果area为空则默认为86
//得到的结果是+area-phone，e.g. +86-13111111111
//国家冠字使用+
//说明：国家冠子是从中国往国外拨打电话时候的前缀统一都是00或者+
// fullphone 手机号，stateCrest国家冠子 00或者+，exclude86 中国的手机号是否排除掉，因为国内打国内不需要国家冠子和86
func FormatPhoneForCall(ctx context.Context, fullphone string, stateCrest string, exclude86 bool) string {
	area, phone := ParseFullPhone(ctx, fullphone, true)
	if phone == "" {
		return ""
	}
	//如果没有area认为是国内86的号
	if area == "" {
		area = "86"
	}
	if area == "86" && exclude86 {
		stateCrest = ""
		area = ""
	}
	area = strings.TrimSpace(area)
	phone = strings.TrimSpace(phone)
	//手机号前面带0的需要全部去掉
	phone = strings.Trim(phone, "0")
	//拨打的时候中间的-会替换掉
	return fmt.Sprintf("%s%s%s", stateCrest, area, phone)
}

// 校验手机号
// 手机号格式：区号-手机号码 或 手机号码。
// 只有手机号码而没有区号，则默认按照区号为86处理。
// 测试的手机号（721开头的）是可以通过校验的。
// 通过校验 返回 true，否则返回 false。
func CheckPhone(phone string) bool {
	phoneRegexp := regexp.MustCompile(`^(([1-9][0-9]*-([1-9][0-9]{5,10}))|([1][0-9]{10})|(721[0-9]{8}))$`)
	return phoneRegexp.MatchString(phone)
}

const (
	VER int32 = 1
)

type SaltInfo struct {
	Salt    string
	Version int32
}

/*
	加密:	根据salt和iv对code进行加密
	code:	需要加密的字符串
	salt:	盐值
	iv:		偏移量（长度需要与 salt 保持一致，否则会 panic）
*/
func Encode(ctx context.Context, code, salt, iv string) string {
	return encodeWithSalt(ctx, code, salt, iv, VER)
}

/*
	加密:	根据salt和iv对code进行加密
	code:	需要加密的字符串
	salt:	盐值
	iv:		偏移量（长度需要与 salt 保持一致，否则会 panic）
	ver:	版本
*/
func EncodeWithVer(ctx context.Context, code, salt, iv string, ver int32) string {
	return encodeWithSalt(ctx, code, salt, iv, ver)
}

func encodeWithSalt(ctx context.Context, code, salt, iv string, ver int32) string {
	fun := "encodeWithSalt -->"
	if code == "" {
		return ""
	}

	sign, err := CBCPKCS5PaddingAesEncrypt([]byte(salt), []byte(iv), []byte(code))
	if err != nil {
		xlog.Errorf(ctx, "%s AesEncryptString err :code:%s,ver:%d,err:%v", fun, code, VER, err)
		return ""
	}
	randstr := "12345678"
	info := &PhoneInfo{
		Version: VER,
		Nonce:   randstr,
		Sign:    sign,
	}
	body, err := proto.Marshal(info)
	if err != nil {
		xlog.Errorf(ctx, "%s protobuf encrypt err :code:%s,ver:%d,err:%v", fun, code, VER, err)
		return ""
	}
	base64Content := base64.StdEncoding.EncodeToString(body)
	return base64Content
}

/*
	解密: 	根据salt和iv对encode进行解密
	encode:	需要解密的加密字符串
	salt: 	盐值
	iv:		偏移量（长度需要与 salt 保持一致，否则会 panic）
	解密的salt 和iv 需要与加密时保持一致，否则会 panic
*/
func Decode(ctx context.Context, encode, salt, iv string) string {
	fun := "decode -->"
	if encode == "" {
		xlog.Infof(ctx, "%s empty decode phone do nothing :encode:%s ", fun, encode)
		return ""
	}
	ver, sign := GetEncodeVer(encode)
	code, err := DecodeBySign(ctx, salt, iv, sign)
	if err != nil {
		xlog.Errorf(ctx, "%s AesDecryptString err :encode:%s,ver:%d,err:%v", fun, encode, ver, err)
		return ""
	}
	return code
}

// 通过sign解密
func DecodeBySign(ctx context.Context, salt, iv string, sign []byte) (string, error) {
	if len(sign) == 0 {
		return "", errors.New("sign empty")
	}
	code, err := CBCPKCS5PaddingAesDecrypt([]byte(salt), []byte(iv), sign)
	if err != nil {
		return "", err
	}
	return string(code), nil
}

// 获取加密串的ver和sign
func GetEncodeVer(encode string) (ver int32, sign []byte) {
	if encode == "" {
		return 0, nil
	}
	enPhoneBytes, err := base64.StdEncoding.DecodeString(encode)
	if err != nil {
		return 0, nil
	}
	info := &PhoneInfo{}
	err = proto.Unmarshal(enPhoneBytes, info)
	if err != nil {
		return 0, nil
	}
	ver = info.Version
	sign = info.Sign
	return
}

// 手机号码部分前置0去掉
func TrimLeft0PhoneNum(phone string) (area string, number string) {
	area = ""
	idx := strings.Index(phone, "-")
	if idx == -1 {
		number = phone
	} else {
		area = phone[:idx]
		number = phone[idx+1:]
	}
	number = strings.TrimLeft(number, "0")
	return
}

// 手机号码部分前置0去掉
func SplitTrimLeft0PhoneNum(phone string, isDefault86 bool) (area string, number string) {
	area = ""
	if isDefault86 {
		area = "86"
	}
	idx := strings.Index(phone, "-")
	if idx == -1 {
		number = phone
	} else {
		area = phone[:idx]
		number = phone[idx+1:]
	}
	number = strings.TrimLeft(number, "0")
	return
}

// 手机号码部分前置0去掉
func TrimLeft0PhoneNumFormat(phone string, isDefault86 bool) string {
	area, num := SplitTrimLeft0PhoneNum(phone, isDefault86)
	if len(area) == 0 {
		return fmt.Sprintf("%s", num)
	}
	return fmt.Sprintf("%s-%s", area, num)
}

// 手机号脱敏
func AnonymityPhone(phone string, withoutArea bool) string {
	area, num := SplitPhone2AreaPhone(phone, true)
	display := "****"
	if len(num) > 6 {
		num = num[:3] + display + num[7:]
	}
	if withoutArea {
		return num
	}
	return fmt.Sprintf("%s-%s", area, num)
}
